<!DOCTYPE html>


<html lang="zh-Hans">


<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="代码,生活,杂记,思考" />
   
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    关于浏览器的一些原理 |  AURORA
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

<link rel="alternate" href="/atom.xml" title="AURORA" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-关于浏览器的一些原理"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  关于浏览器的一些原理
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/03/04/%E5%85%B3%E4%BA%8E%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E4%B8%80%E4%BA%9B%E5%8E%9F%E7%90%86/" class="article-date">
  <time datetime="2020-03-03T16:00:00.000Z" itemprop="datePublished">2020-03-04 00:03:00</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E5%89%8D%E7%AB%AF/">前端</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E5%89%8D%E7%AB%AF/HTML%E3%80%81%E6%B5%8F%E8%A7%88%E5%99%A8%E7%BB%BC%E5%90%88/">HTML、浏览器综合</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">4.3k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">15 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="浏览器的内核"><a href="#浏览器的内核" class="headerlink" title="浏览器的内核"></a>浏览器的内核</h1><a id="more"></a>
<p>一个完整的浏览器内核主要分成两部分：<strong>渲染引擎(layout engineer或Rendering Engine)和JS引擎</strong></p>
<p>渲染引擎：负责取得网页的内容（HTML、XML、图像等等）、整理讯息（例如加入CSS等），以及计算网页的显示方式，然后会输出至显示器或打印机。<strong>浏览器的内核的不同对于网页的语法解释会有不同，所以渲染的效果也不相同</strong>。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核</p>
<p>JS引擎则：解析和执行javascript来实现网页的动态效果</p>
<p><strong>最开始渲染引擎和JS引擎并没有区分的很明确，后来JS引擎越来越独立，内核就倾向于只指渲染引擎， 到如今。我们所指的浏览器内核，一般就是指渲染引擎</strong>（下文所指的内核，若无特殊提示，则代表渲染引擎）</p>
<h2 id="渲染引擎"><a href="#渲染引擎" class="headerlink" title="渲染引擎"></a>渲染引擎</h2><p>目前使用的主流浏览器有五个：Internet Explorer、 Firefox、 Safari、 Chrome 和 Opera，他们使用的渲染引擎分别如下：</p>
<ul>
<li>Trident引擎：Internet Explorer</li>
<li>Webkit引擎：Chrome（28版本后基于blink，blink是webkit的一个分支）和Safari</li>
<li>Gecko 英[ˈɡekəʊ]引擎：Firefox</li>
<li>Presto引擎：早期Opera采用，后用webkit引擎</li>
</ul>
<h1 id="浏览器内核渲染机制"><a href="#浏览器内核渲染机制" class="headerlink" title="浏览器内核渲染机制"></a>浏览器内核渲染机制</h1><p><img src="https://img-blog.csdnimg.cn/20200304114746310.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""></p>
<ul>
<li>处理 HTML 并构建 DOM 树。</li>
<li>处理 CSS 构建 CSSOM 树。</li>
<li>将 DOM 与 CSSOM 合并成一个渲染树。</li>
<li>Layout(回流):根据生成的渲染树，进行回流(Layout)，得到节点的几何信息（位置，大小）</li>
<li>Painting(重绘):根据渲染树以及回流得到的几何信息，得到节点的绝对像素</li>
<li>Display:将像素发送给GPU，展示在页面上。（这一步其实还有很多内容，比如会在GPU将多个合成层合并为同一个层，并展示在页面中。<strong>而css3硬件加速的原理则是新建合成层</strong>）</li>
</ul>
<h2 id="构建DOM树"><a href="#构建DOM树" class="headerlink" title="构建DOM树"></a>构建DOM树</h2><ul>
<li>当我们打开一个网页时，浏览器都会去请求对应的 HTML 文件。虽然平时我们写代码时都会分为 JS、CSS、HTML 文件，也就是字符串，但是计算机硬件是不理解这些字符串的，所以在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据以后，它会将这些字节数据转换为字符串，也就是我们写的代码。</li>
</ul>
<ul>
<li>当数据转换为字符串以后，浏览器会先将这些字符串通过词法分析转换为标记（token），这一过程在词法分析中叫做标记化（tokenization）</li>
</ul>
<ul>
<li>简单来说，标记还是字符串，是构成代码的最小单位。这一过程会将代码分拆成一块块，并给这些内容打上标记，便于理解这些最小单位的代码是什么意思</li>
</ul>
<ul>
<li>当结束标记化后，这些标记会紧接着转换为 Node，最后这些 Node 会根据不同 Node 之前的联系构建为一颗 DOM 树</li>
</ul>
<h2 id="构建CSSOM树"><a href="#构建CSSOM树" class="headerlink" title="构建CSSOM树"></a>构建CSSOM树</h2><p>这一过程大体流程和上面的类似</p>
<p>在这一过程中，浏览器会确定下每一个节点的样式到底是什么，并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点，也可以通过继承获得。在这一过程中，浏览器得递归 CSSOM 树，然后确定具体的元素到底是什么样式</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;div&gt;</span><br><span class="line">  &lt;a&gt; &lt;span&gt;&lt;/span&gt; &lt;/a&gt;</span><br><span class="line">&lt;/div&gt;</span><br><span class="line">&lt;style&gt;</span><br><span class="line">  span &#123;</span><br><span class="line">    color: red;</span><br><span class="line">  &#125;</span><br><span class="line">  div &gt; a &gt; span &#123;</span><br><span class="line">    color: red;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/style&gt;</span><br></pre></td></tr></table></figure>
<p>对于第一种设置样式的方式来说，浏览器只需要找到页面中所有的 span 标签然后设置颜色，但是对于第二种设置样式的方式来说，浏览器首先需要找到所有的 span 标签，然后找到 span 标签上的 a 标签，最后再去找到 div 标签，然后给符合这种条件的 span 标签设置颜色，这样的递归过程就很复杂。</p>
<p><strong> 在构建 CSSOM 树时，会阻塞渲染，直至 CSSOM 树构建完成，所以我们应该尽可能的避免写过于具体的 CSS 选择器，然后对于 HTML 来说也尽量少的添加无意义标签，保证层级扁平</strong></p>
<h2 id="构建渲染树"><a href="#构建渲染树" class="headerlink" title="构建渲染树"></a>构建渲染树</h2><ul>
<li>从DOM树的根节点开始<strong>遍历每个可见节点</strong>，例如css的display：none就是个不可见节点，不会进入渲染树。</li>
<li>对于每个可见的节点，找到CSSOM树中对应的规则，并应用它们。</li>
<li>根据每个可见节点以及其对应的样式，组合生成渲染树。</li>
</ul>
<p>第一步中，既然说到了要遍历可见的节点，那么我们得先知道，什么节点是不可见的。不可见的节点包括：</p>
<ul>
<li><strong>一些不会渲染输出的节点，比如script、meta、link等</strong>。</li>
<li><strong>一些通过css进行隐藏的节点。比如display:none。注意，利用visibility和opacity隐藏的节点，还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上</strong>。</li>
</ul>
<p>总结：渲染树只包含可见的节点</p>
<h2 id="回流与重绘"><a href="#回流与重绘" class="headerlink" title="回流与重绘"></a>回流与重绘</h2><h3 id="回流"><a href="#回流" class="headerlink" title="回流"></a>回流</h3><p>前面我们通过构造渲染树，我们将可见DOM节点以及它对应的样式结合起来，可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小，这个计算的阶段就是回流。</p>
<h3 id="重绘"><a href="#重绘" class="headerlink" title="重绘"></a>重绘</h3><p>最终，我们通过构造渲染树和回流阶段，我们知道了哪些节点是可见的，以及可见节点的样式和具体的几何信息(位置、大小)，那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素，这个阶段就叫做重绘节点。</p>
<h3 id="回流与重绘的触发机制"><a href="#回流与重绘的触发机制" class="headerlink" title="回流与重绘的触发机制"></a>回流与重绘的触发机制</h3><p>我们前面知道了，回流这一阶段主要是计算节点的位置和几何信息，那么当页面布局和几何信息发生变化的时候，就需要回流。比如以下情况：</p>
<ul>
<li>添加或删除可见的DOM元素</li>
<li>元素的位置发生变化</li>
<li>元素的尺寸发生变化（包括外边距、内边框、边框大小、高度和宽度等）</li>
<li>内容发生变化，比如文本变化或图片被另一个不同尺寸的图片所替代。</li>
<li>页面一开始渲染的时候（这肯定避免不了）</li>
<li>浏览器的窗口尺寸变化（因为回流是根据视口的大小来计算元素的位置和大小的）</li>
</ul>
<p><strong>注意：回流一定会触发重绘，而重绘不一定会回流</strong></p>
<p>根据改变的范围和程度，渲染树中或大或小的部分需要重新计算，有些改变会触发整个页面的重排，比如，滚动条出现的时候或者修改了根节点。</p>
<p>现代的浏览器都是很聪明的，由于每次重排都会造成额外的计算消耗，因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里，直到过了一段时间或者操作达到了一个阈值，才清空队列。但是！当你获取布局信息的操作的时候，会强制队列刷新，比如当你访问以下属性或者使用以下方法：</p>
<p><strong>offsetTop、offsetLeft、offsetWidth、offsetHeight<br>scrollTop、scrollLeft、scrollWidth、scrollHeight<br>clientTop、clientLeft、clientWidth、clientHeight<br>getComputedStyle()<br>getBoundingClientRect</strong></p>
<p>以上属性和方法都需要返回最新的布局信息，<strong>因此浏览器不得不清空队列，触发回流重绘来返回正确的值</strong>。因此，我们在修改样式的时候，最好避免使用上面列出的属性，他们都会刷新渲染队列。如果要使用它们，最好将值缓存起来。</p>
<h3 id="减少回流与重绘"><a href="#减少回流与重绘" class="headerlink" title="减少回流与重绘"></a>减少回流与重绘</h3><h4 id="使用cssText或者className一次性改变属性"><a href="#使用cssText或者className一次性改变属性" class="headerlink" title="使用cssText或者className一次性改变属性"></a>使用cssText或者className一次性改变属性</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">const el = document.getElementById('test');</span><br><span class="line">el.style.padding = '5px';</span><br><span class="line">el.style.borderLeft = '1px';</span><br><span class="line">el.style.borderRight = '2px';</span><br></pre></td></tr></table></figure>
<p>替换为</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">const el = document.getElementById('test');</span><br><span class="line">el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';</span><br></pre></td></tr></table></figure>
<h4 id="批量操作时先使元素脱离文档流再修改"><a href="#批量操作时先使元素脱离文档流再修改" class="headerlink" title="批量操作时先使元素脱离文档流再修改"></a>批量操作时先使元素脱离文档流再修改</h4><p>当我们进行DOM操作，特别是批量操作时，可以使用如下步骤：</p>
<ul>
<li>使元素脱离文档流</li>
<li>对其进行多次修改</li>
<li>将元素带回到文档中。</li>
</ul>
<p>对于批量操作，避免在循环内部一次又一次地进行dom操作，尽量将多次dom操作合并为一次</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">appendDataToElement</span>(<span class="params">appendToElement, data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> li;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; data.length; i++) &#123;</span><br><span class="line">        li = <span class="built_in">document</span>.createElement(<span class="string">'li'</span>);</span><br><span class="line">        li.textContent = <span class="string">'text'</span>;</span><br><span class="line">        appendToElement.appendChild(li);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ul = <span class="built_in">document</span>.getElementById(<span class="string">'list'</span>);</span><br><span class="line">appendDataToElement(ul, data);</span><br></pre></td></tr></table></figure>
<p>替换为：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">appendDataToElement</span>(<span class="params">appendToElement, data</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> li;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; data.length; i++) &#123;</span><br><span class="line">        li = <span class="built_in">document</span>.createElement(<span class="string">'li'</span>);</span><br><span class="line">        li.textContent = <span class="string">'text'</span>;</span><br><span class="line">        appendToElement.appendChild(li);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> ul = <span class="built_in">document</span>.getElementById(<span class="string">'list'</span>);</span><br><span class="line">ul.style.display = <span class="string">'none'</span>;</span><br><span class="line">appendDataToElement(ul, data);</span><br><span class="line">ul.style.display = <span class="string">'block'</span>;</span><br></pre></td></tr></table></figure>
<p>然而对于上述那种情况，实验结果不是很理想。</p>
<p>原因：原因其实上面也说过了，浏览器会使用队列来储存多次修改，进行优化，所以对这个优化方案，我们其实不用优先考虑。</p>
<h4 id="对于复杂动画效果-使用绝对定位让其脱离文档流"><a href="#对于复杂动画效果-使用绝对定位让其脱离文档流" class="headerlink" title="对于复杂动画效果,使用绝对定位让其脱离文档流"></a>对于复杂动画效果,使用绝对定位让其脱离文档流</h4><p>对于复杂动画效果，由于会经常的引起回流重绘，因此，我们可以使用绝对定位，让它脱离文档流。否则会引起父元素以及后续元素频繁的回流</p>
<h4 id="使用-visibility-替换display-none-，因为前者只会引起重绘，后者会引发回流（改变了布局）"><a href="#使用-visibility-替换display-none-，因为前者只会引起重绘，后者会引发回流（改变了布局）" class="headerlink" title="使用 visibility 替换display: none ，因为前者只会引起重绘，后者会引发回流（改变了布局）"></a>使用 visibility 替换display: none ，因为前者只会引起重绘，后者会引发回流（改变了布局）</h4><h4 id="css3硬件加速"><a href="#css3硬件加速" class="headerlink" title="css3硬件加速"></a>css3硬件加速</h4><p>比起考虑如何减少回流重绘，我们更期望的是，根本不要回流重绘。这个时候，css3硬件加速就闪亮登场啦！！</p>
<p>划重点：<strong>使用css3硬件加速，可以让transform、opacity、filters这些动画不会引起回流重绘</strong> 。但是对于动画的其它属性，比如background-color这些，还是会引起回流重绘的，不过它还是可以提升这些动画的性能。</p>
<p>常见的触发硬件加速的css属性：</p>
<ul>
<li>transform</li>
<li>opacity</li>
<li>filters</li>
<li>Will-change</li>
</ul>
<p>缺点：占用较大，会有性能问题</p>
<h4 id="尽量避免使用table布局"><a href="#尽量避免使用table布局" class="headerlink" title="尽量避免使用table布局"></a>尽量避免使用table布局</h4><p>一个很小的改动都有可能造成table布局的回流</p>
<h4 id="将频繁重绘或者回流的节点设置为图层"><a href="#将频繁重绘或者回流的节点设置为图层" class="headerlink" title="将频繁重绘或者回流的节点设置为图层"></a>将频繁重绘或者回流的节点设置为图层</h4><p>图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说，浏览器会自动将该节点变为图层</p>
<blockquote>
<p>一般来说，可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响，所以对于某些频繁需要渲染的建议单独生成一个新图层，提高性能。但也不能生成过多的图层，会引起反作用</p>
</blockquote>
<p>通过以下几个常用属性可以生成新图层</p>
<ul>
<li>3D变换：translate3d、translateZ</li>
<li>will-change</li>
<li>video、iframe 标签</li>
<li>通过动画实现的 opacity 动画转换</li>
<li>position: fixed</li>
</ul>
<h3 id="回流重绘与event-loop"><a href="#回流重绘与event-loop" class="headerlink" title="回流重绘与event loop"></a>回流重绘与event loop</h3><ul>
<li>当 Eventloop 执行完 Microtasks 后，会判断 document 是否需要更新，因为浏览器是 60Hz 的刷新率，每 16.6ms 才会更新一次。</li>
<li>然后判断是否有 resize 或者 scroll 事件，有的话会去触发事件，<strong>所以 resize 和 scroll 事件也是至少 16ms 才会触发一次</strong>，<strong>自带节流功能</strong>。</li>
<li>判断是否触发了 media query</li>
<li>更新动画并且发送事件</li>
<li>判断是否有全屏操作事件</li>
<li>执行 requestAnimationFrame回调</li>
<li>执行 IntersectionObserver 回调，该方法用于判断元素是否可见，可以用于懒加载上，但是兼容性不好 更新界面</li>
<li>以上就是一帧中可能会做的事情。如果在一帧中有空闲时间，就会去执行 requestIdleCallback回调</li>
</ul>
<h2 id="什么情况阻塞渲染"><a href="#什么情况阻塞渲染" class="headerlink" title="什么情况阻塞渲染"></a>什么情况阻塞渲染</h2><ul>
<li>首先渲染的前提是生成渲染树，所以 HTML 和 CSS 肯定会阻塞渲染，换句话说，<strong>构建DOM树和CSS树都会阻塞渲染</strong>。如果你想渲染的越快，你越应该降低一开始需要渲染的文件大小，并且扁平层级，优化选择器。</li>
<li>然后当浏览器在解析到 script 标签时，会暂停构建 DOM，完成后才会从暂停的地方重新开始。也就是说，如果你想首屏渲染的越快，就越不应该在首屏就加载 JS文件，这也是都建议将 script 标签放在 body 标签底部的原因。</li>
<li>当然在当下，并不是说 script 标签必须放在底部，<strong>因为你可以给 script 标签添加 defer 或者 async 属性</strong>。</li>
<li>当 script 标签加上 defer 属性以后，表示该 JS 文件会并行下载，但是会放到 HTML 解析完成后顺序执行，所以对于这种情况你可以把 script标签放在任意位置。</li>
<li>对于没有任何依赖的 JS 文件可以加上 async 属性，表示 JS 文件下载和解析不会阻塞渲染</li>
</ul>
<h1 id="浏览器从输入url到显示发生了什么"><a href="#浏览器从输入url到显示发生了什么" class="headerlink" title="浏览器从输入url到显示发生了什么"></a>浏览器从输入url到显示发生了什么</h1><ol>
<li>在浏览器地址栏输入URL</li>
<li>浏览器查看缓存，如果请求资源在缓存中并且未过期，跳转到转码步骤</li>
<li>浏览器对于缓存的读取是有优先级的（关于缓存，另一篇博客单独讲述）</li>
</ol>
<ul>
<li>Service Worker</li>
<li>Memory Cache（内存中的缓存）</li>
<li>Disk Cache（硬盘中的缓存）</li>
<li>网络请求</li>
</ul>
<ol start="4">
<li>如果资源未缓存（没有Memory Cache），发起新请求</li>
<li>检测是否有缓存（Disk Cache），如果有，检验是否未过期，足够新鲜直接提供给客户端，否则与服务器进行验证。</li>
<li>这里的检验缓存是否过期通常有两个HTTP头进行控制Expires和Cache-Control：</li>
<li>HTTP1.0提供Expires，值为一个绝对时间表示缓存到期时间</li>
<li>HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的有效时间</li>
<li>浏览器解析URL获取协议，主机，端口，path</li>
<li>浏览器组装一个HTTP（GET）请求报文</li>
<li>浏览器获取主机ip地址，过程如下：</li>
</ol>
<ul>
<li>浏览器缓存</li>
<li>本机缓存</li>
<li>hosts文件</li>
<li>路由器缓存</li>
<li>运营商 DNS缓存</li>
<li>DNS递归查询（可能存在负载均衡导致每次IP不一样）</li>
</ul>
<ol start="12">
<li>打开一个socket与目标IP地址，端口建立TCP链接，三次握手如下：</li>
</ol>
<ul>
<li>客户端发送一个TCP的SYN=1，Seq=X的包到服务器端口</li>
<li>服务器发回SYN=1， ACK=X+1， Seq=Y的响应包</li>
<li>客户端发送ACK=Y+1， Seq=Z<br>（TCP三次握手四次挥手，会在另一篇博客单独讲述）</li>
</ul>
<ol start="13">
<li>TCP链接建立后发送HTTP请求</li>
<li>服务器接受请求并解析，将请求转发到服务程序，如虚拟主机使用HTTP Host头部判断请求的服务程序<br>服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜，返回304等对应状态码</li>
<li>处理程序读取完整请求并准备HTTP响应，可能需要查询数据库等操作</li>
<li>服务器将响应报文通过TCP连接发送回浏览器</li>
<li>浏览器接收HTTP响应，然后根据情况选择关闭TCP连接或者保留重用，关闭TCP连接的四次握手如下：</li>
</ol>
<ul>
<li>主动方发送Fin=1， Ack=Z， Seq= X报文</li>
<li>被动方发送ACK=X+1， Seq=Z报文</li>
<li>被动方发送Fin=1， ACK=X， Seq=Y报文</li>
<li>主动方发送ACK=Y， Seq=X报文</li>
</ul>
<ol start="18">
<li>浏览器检查响应状态吗：是否为1XX，3XX， 4XX， 5XX，这些情况处理与2XX不同</li>
<li>如果资源可缓存，进行缓存</li>
<li>对响应进行解码（例如gzip压缩）</li>
<li>根据资源类型决定如何处理（假设资源为HTML文档）</li>
<li>解析HTML文档，构件DOM树，下载资源，构造CSSOM树，执行js脚本，这些操作没有严格的先后顺序，以下分别解释</li>
</ol>
<blockquote>
<p>构建DOM树：通过词法分析进行标记化，再将标记变成Node节点，由Node节点组成DOM树<br>construction：根据HTML标记关系将对象组成DOM树 解析过程中遇到图片、样式表、js文件，启动下载</p>
</blockquote>
<blockquote>
<p>构建CSSOM树： Tokenizing：字符流转换为标记流 Node：根据标记创建节点 CSSOM：节点创建CSSOM树</p>
</blockquote>
<blockquote>
<p>根据DOM树和CSSOM树构建渲染树:</p>
</blockquote>
<blockquote>
<p>回流：将可见DOM节点以及它对应的样式结合起来，计算它们在设备视口(viewport)内的确切位置和大小</p>
</blockquote>
<blockquote>
<p>重绘： 知道了哪些节点是可见的，以及可见节点的样式和具体的几何信息(位置、大小)，将渲染树的每个节点都转换为屏幕上的实际像素。</p>
</blockquote>
<blockquote>
<p>复合图层的合成、GPU绘制、外链资源的处理</p>
</blockquote>
<ol start="22">
<li>JS引擎解析过程（JS的解释阶段，预处理阶段，执行阶段生成执行上下文，作用域链、回收机制等等</li>
<li>显示页面（HTML解析过程中会逐步显示页面）</li>
</ol>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>Copyright： </strong>
          Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/03/05/%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E7%BC%93%E5%AD%98%E4%B8%8E%E5%AD%98%E5%82%A8/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            浏览器的缓存与存储
          
        </div>
      </a>
    
    
      <a href="/2020/03/02/JS%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">JS异步编程</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "2eBxdPB3eEwraN7IpwFckzeL-gzGzoHsz",
    app_key: "VGgCefQEluVxHdyMsm1Dpeih",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "评论一下我的文章吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> AURORA_ZXH
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        Powered by <a href="https://hexo.io" target="_blank">Hexo</a>
        <span class="division">|</span>
        Theme - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/avatar.jpg" alt="AURORA"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://aurora-zxh.lofter.com/" target="_blank" rel="noopener">随记</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/link">友链</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.png">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wxpay.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>